﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using Silvermoon.Controls.Base;
using Silvermoon.Shapes;
using Silvermoon.Windows.Styles;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Item container control to group items in a <see cref="ListView"/>.
    /// </summary>
    public class ListViewGroup : Item, IItemHost
    {
        #region fields

        private int selectedItemIndex=-1;
        private ListViewItemAnimator animator = new ListViewItemAnimator();
        private ItemsContainer itemContainer;
        private int totalHeight;
        private int itemsPerRow = 1;
        private IList itemsSource;
        private int itemHeight = 80;
        private Dictionary<int, Item> virtualItems = new Dictionary<int, Item>();
        private Control header;
        private ControlCollection visibleControls;
        private int itemFrameBuffer = 0;
        //private ListViewGroupListManager listManager;

        #endregion
        #region ctor/dtor

        public ListViewGroup()
            : base()
        {
            //listManager = new ListViewGroupListManager(this);
            visibleControls = new ControlCollection(this);
            itemContainer = new ItemsContainer();
            visibleControls.Add(itemContainer);
        }

        protected override void Dispose(bool dispose)
        {
            ItemsSource = null;
            DisposeVisibleItems();
            base.Dispose(dispose);
        }

        #endregion
        #region props

        /// <summary>
        /// Gets all the items which are currently attached to the group.
        /// </summary>
        public IEnumerable<Item> AttachedItems
        {
            get { return virtualItems.Values; }
        }

        public int SelectedItemIndex
        {
            get { return selectedItemIndex; }
            set
            {
                if (selectedItemIndex != value)
                {
                    selectedItemIndex = value;
                    OnSelectionChanged(true);
                }
            }
        }

        protected ListView ListView { get; private set; }

        protected int TotalHeight
        {
            get { return totalHeight; }
            set
            {
                if (totalHeight != value)
                {
                    if (IsDisposing) return;
                    totalHeight = value;
                    CalculateHeight();
                }
            }
        }

        /// <summary>
        /// Gets or sets how many items to keep before they are released when they are outside the visible area.
        /// </summary>
        public int ItemFrameBuffer
        {
            get { return itemFrameBuffer; }
            set
            {
                if (value < 0) value = 0;
                itemFrameBuffer = value;
            }
        }

        public Shape ContainerBackground
        {
            get { return itemContainer.Background; }
            set
            {
                if (itemContainer.Background != value)
                {
                    itemContainer.Background = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        protected bool IsAnimatable
        {
            get { return ItemsPerRow == 1 && animator.IsAnimated; }
        }

        /// <summary>
        /// Gets or sets the number of items to display in one row.
        /// </summary>
        public int ItemsPerRow
        {
            get { return itemsPerRow; }
            set
            {
                if (value < 1) value = 1;
                if (value != itemsPerRow)
                {
                    int oldValue = itemsPerRow;
                    itemsPerRow = value;
                    OnColumnCountChanged(oldValue);
                }
            }
        }


        /// <summary>
        /// Gets the width of a column which depends on the bounds of the listview and the number of items per row.
        /// </summary>
        public int ItemWidth
        {
            get { return ClientBounds.Width / itemsPerRow; }
        }

        /// <summary>
        /// Gets or sets the height for each item.
        /// Note that in a group, each item must have the same size while in a <see cref="ListView"/>, the height is dynamic.
        /// </summary>
        public int ItemHeight
        {
            get { return itemHeight; }
            set
            {
                if (itemHeight != value)
                {
                    int oldValue = value;
                    itemHeight = value;
                    OnItemHeightChanged(oldValue);
                }
            }
        }


        /// <summary>
        /// Gets the number of data begin visualized in the group.
        /// </summary>
        public int Count
        {
            get { return itemsSource != null ? itemsSource.Count : 0; }
        }

        /// <summary>
        /// Gets the height of the header.
        /// </summary>
        public int HeaderHeight
        {
            get { return header != null ? header.Height + header.Margin.Height : 0; }
        }

        /// <summary>
        /// Gets or sets the items source that contains data to display.
        /// </summary>
        public IList ItemsSource
        {
            get { return itemsSource; }
            set
            {
                if (itemsSource != value)
                {
                    IList oldValue = value;
                    itemsSource = value;
                    OnItemsChanged(oldValue);
                }
            }
        }

        /// <summary>
        /// Gets or sets the control to be displayed topmost of all items.
        /// </summary>
        public Control Header
        {
            get { return header; }
            set
            {
                if (header != value)
                {
                    Control oldValue = header;
                    header = value;
                    OnHeaderChanged(oldValue);
                }
            }
        }


        #endregion
        #region methods

        protected override void OnHostChanged()
        {
            base.OnHostChanged();
            ListView = (ListView)Host;
        }

        private void CalculateHeight()
        {
            Height = totalHeight + HeaderHeight + animator.GetTotalOffset();
        }

        protected virtual void OnHeaderChanged(Control oldValue)
        {
            if (oldValue != null) VisibleControls.Remove(oldValue);
            if (header != null) VisibleControls.Add(header);
            Height = totalHeight + HeaderHeight;
        }

        private void OnItemsChanged(IList oldValue)
        {
            IBindingList bindingList = oldValue as IBindingList;
            if (bindingList != null) bindingList.ListChanged -= OnBindingListChanged;

            if (itemsSource == null)
            {
                TotalHeight = 0;
            }
            else
            {
                bindingList = itemsSource as IBindingList;
                if (bindingList != null)
                {
                    bindingList.ListChanged += new ListChangedEventHandler(OnBindingListChanged);
                }
                Rectangle bounds = GetItemRect(Count - 1);
                TotalHeight = bounds.Bottom;

            }
            Invalidate(InvalidationFlag.Layout);
        }

        public virtual Rectangle GetItemRect(int index)
        {
            if (itemsPerRow == 1)
            {
                return new Rectangle(0, index * itemHeight, ItemWidth, itemHeight);
            }
            else
            {
                int w = ItemWidth;
                int h = itemHeight;
                int x = index % itemsPerRow;
                int y = index / itemsPerRow;
                return new Rectangle(x * w, y * h, w, h);
            }
        }

        protected virtual void OnBindingListChanged(object sender, ListChangedEventArgs e)
        {
            OnItemsChanged(null);
        }

        protected virtual void DisposeVisibleItems()
        {
            itemContainer.VisibleControls.Clear();
            foreach (Item item in virtualItems.Values)
            {
                OnReleaseVirtualItem(item);
            }
            virtualItems.Clear();
        }

        protected virtual void OnReleaseVirtualItem(Item item)
        {
            ListView.OnReleaseVirtualItem(item);
        }

        protected virtual Item OnGetVirtualItem(int index, object data)
        {
            Item vItem = data as Item;
            if (vItem != null) return vItem;
            if (virtualItems.TryGetValue(index, out vItem)) return vItem;

            return ListView.OnGetVirtualItem(index, data, this);
        }


        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size))
            {
                if (header != null)
                {
                    header.Bounds = GetHeaderRect();
                }
                LayoutItems();
                itemContainer.Bounds = GetContainerRect();
            }
            base.OnLayout();
        }

        protected virtual Rectangle GetContainerRect()
        {
            int ch = ClientHeight;
            int h = HeaderHeight;
            return new Rectangle(0, h, ClientWidth, ch - h);
        }

        public virtual Rectangle GetHeaderRect()
        {
            if (header == null) return Rectangle.Empty;
            int y = 0;
            Margin m = header.Margin;
            int h = HeaderHeight;
            int ch = ClientHeight;
            if (Top < 0 && ListView.AlwaysShowGroupHeader)
            {
                y = -Top;
                if ((y + h) > ch) y = ch - h;
            }
            Size size = new Size(ClientWidth, h);
            Rectangle r = m.Layout(header.Bounds, size);
            r.Y += y;
            return r;
        }

        private List<int> removals = new List<int>();

        private void ReleaseItems(Rectangle virtualizedRect)
        {
            Rectangle area = virtualizedRect;
            int count = Count;
            if (itemFrameBuffer > 0) area.Inflate(0, itemFrameBuffer * ItemHeight);
            var visibleControls = itemContainer.VisibleControls;
            foreach (int index in virtualItems.Keys)
            {
                Rectangle itemRect = GetItemRect(index);
                if (index >= count || !area.IntersectsWith(itemRect))
                {
                    Item item = virtualItems[index];
                    OnReleaseVirtualItem(item);
                    visibleControls.Remove(item);
                    removals.Add(index);
                }
            }
            foreach (int index in removals)
            {
                virtualItems.Remove(index);
            }
            removals.Clear();
        }

        /// <summary>
        /// Gets the rectangle which is currently visible, relative to the first item's offset.
        /// </summary>
        /// <returns></returns>
        protected Rectangle GetVirtualizedRect()
        {
            Rectangle parentBounds = Parent.ClientBounds;
            //int top = Math.Max(0, -HeaderHeight - Top);
            int top = -HeaderHeight - Top;
            int bottom = Math.Min(parentBounds.Height - Top, ClientHeight) - HeaderHeight;
            return new Rectangle(0, top, ClientWidth, Math.Max(0, bottom - top));
        }

        /// <summary>
        /// Layouts the visible items in the container.
        /// </summary>
        private void LayoutItems()
        {
            Control parent = Parent;
            if (parent == null) return;

            animator.ProcessAnimationState();
            CalculateHeight();

            Rectangle virtualizedRect = GetVirtualizedRect();
            ReleaseItems(virtualizedRect);
            int yOffset = virtualizedRect.Top;

            var visibleControls = itemContainer.VisibleControls;
            visibleControls.ClearQuick();
            int count = Count;
            if (count > 0)
            {
                int index = GetItemAtOffset(yOffset);
                if (IsAnimatable) index = OffsetIndex(index);
                if (index >= 0)
                {
                    int max = virtualizedRect.Bottom;
                    int w = ClientWidth;
                    Rectangle bounds = GetItemRect(index);
                    bool isAnimated = IsAnimatable;
                    while (bounds.Top < max && index < count)
                    {
                        object value = itemsSource[index];
                        Item item = OnGetVirtualItem(index, value);
                        visibleControls.Add(item);
                        item.Attach(index, value, this);
                        ListView.OnItemAttached(item);
                        virtualItems[index] = item;

                        //item.Bounds = ItemMargin.Layout(bounds, bounds.Size);
                        Margin m = item.Margin;
                        //Size size = new Size(item.Width + m.Width, item.Height + m.Height);
                        item.Bounds = item.Margin.LayoutChild(bounds);
                        if (isAnimated) animator.AdjustNextItem(item, index);
                        index++;
                        if (index >= Count) break;
                        bounds = GetItemRect(index);

                    }
                }
            }
        }

        private int OffsetIndex(int index)
        {
            int d = animator.GetIndexOffset(index);
            if (d > 0)
            {
                int h = ItemHeight;
                while (d > 0 && index > 0)
                {
                    d -= h;
                    index--;
                }
            }
            else
            {
                int max = Count - 1;
                int h = ItemHeight;
                while (index < max)
                {
                    d += h;
                    if (d > 0) break;
                    index++;
                }
            }
            return Math.Max(0, index);
        }

        protected virtual int GetItemAtOffset(int yOffset)
        {
            int n = yOffset / itemHeight;
            if (n < 0) n = 0;
            return n * itemsPerRow;
        }

        protected internal override void OnRender(Silvermoon.Core.Renderer renderer)
        {
            base.OnRender(renderer);
            renderer.RenderControls(visibleControls);
            if (animator.IsAnimated) Schedule(Invalidate);
        }

        private void Invalidate()
        {
            Invalidate(InvalidationFlag.Layout);
        }


        protected internal override ControlCollection GetVisibleControls()
        {
            return visibleControls;
        }

        protected override void OnSizeChanging(Size oldValue)
        {
            base.OnSizeChanging(oldValue);
            ListView listView = Host as ListView;
            if (listView != null)
            {
                int h = GetItemHeight();
                int h1 = Height + Margin.Height;
                if (oldValue.Height != h1 || h != h1)
                {
                    listView.SetItemHeight(this.ItemIndex, h);
                }
            }
        }

        private int GetItemHeight()
        {
            return totalHeight + HeaderHeight + Margin.Height + animator.GetTotalOffset();
        }



        protected override void OnMarginChanged()
        {
            base.OnMarginChanged();
            int h = Height + Margin.Height;
            IItemHost host = Host;
            if (host != null)
            {
                host.SetItemHeight(this.ItemIndex, h);
            }
        }

        protected override void OnLocationChanged()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        protected virtual void OnItemHeightChanged(int oldValue)
        {
            Invalidate(InvalidationFlag.Layout | InvalidationFlag.Internal);
        }

        protected virtual void OnColumnCountChanged(int oldValue)
        {
            Invalidate(InvalidationFlag.Layout | InvalidationFlag.Internal);
        }

        public Item GetRealizedItem(int index)
        {
            Item result;
            return virtualItems.TryGetValue(index, out result) ? result : null;
        }

        /// <summary>
        /// The item is released from being shown in a <see cref="ListView"/> and is requested to release virtualized items currently alloced.
        /// </summary>
        protected internal override void Release()
        {
            if (virtualItems.Count > 0) Invalidate(InvalidationFlag.Layout);
            foreach (Item item in virtualItems.Values)
            {
                OnReleaseVirtualItem(item);
            }
            virtualItems.Clear();
            itemContainer.VisibleControls.Clear();
        }

        private void CheckIndex(int index)
        {
            if (itemsSource == null) throw new NullReferenceException("ItemsSource is null.");
            if (index < 0 || index > itemsSource.Count) throw new ArgumentOutOfRangeException("index");
        }


        /// <summary>
        /// Removes a value from the list.
        /// </summary>
        /// <param name="index">index of the value to remove</param>
        /// <exception cref="NullRefrerenceException"/> if ItemsSource is null.
        /// <exception cref="ArgumentOutOfRangeException"/> if index is out of range.
        public virtual void RemoveAt(int index)
        {
            CheckIndex(index);
            itemsSource.RemoveAt(index);
            OnItemRemoved(index);
        }


        public virtual void Insert(int index, object value)
        {
            CheckIndex(index);
            itemsSource.Insert(index, value);
            OnItemInserted(index);
        }

        /// <summary>
        /// Occurs when an item was removed from ItemsSource.
        /// </summary>
        /// <param name="index">Index of the removed item.</param>
        public virtual void OnItemRemoved(int index)
        {
            animator.ItemRemoved(index, ItemHeight);

            InvalidateItemsFromIndex(index);
            if (this.Host != null) Host.Invalidate(InvalidationFlag.Layout);
        }


        /// <summary>
        /// Occurs when an item was added to ItemsSource.
        /// </summary>
        /// <param name="index">Index of the added item.</param>
        public virtual void OnItemInserted(int index)
        {
            animator.ItemInserted(index, ItemHeight);
            InvalidateItemsFromIndex(index);
            if (this.Host != null) Host.Invalidate(InvalidationFlag.Layout);
        }

        private void InvalidateItemsFromIndex(int index)
        {
            Invalidate(InvalidationFlag.Layout);
            DisposeVisibleItems();
            //if (virtualItems.Count > 0)
            //{

            //    int[] keys = new int[virtualItems.Count];
            //    int i = 0;
            //    foreach (int key in virtualItems.Keys)
            //    {
            //        keys[i++] = key;
            //    }
            //    bool invalidate = false;
            //    foreach (int key in keys)
            //    {
            //        if (key >= index)
            //        {
            //            invalidate = true;
            //            var item = virtualItems[key];
            //            OnReleaseVirtualItem(item);
            //            virtualItems.Remove(key);
            //        }
            //    }

            //    if (invalidate)
            //    {
            //        Invalidate(InvalidationFlag.Layout);
            //    }
            //}
            Rectangle bounds = GetItemRect(Count - 1);
            TotalHeight = bounds.Bottom;
        }


        /// <summary>
        /// Removes a given value.
        /// </summary>
        /// <param name="value">Value to remove.</param>
        /// <returns>true, if the value within the list, otherwise false.</returns>
        /// <exception cref="NullRefrerenceException">if ItemsSource is null.</exception>
        public bool Remove(object value)
        {
            if (itemsSource == null) throw new NullReferenceException("ItemSource is null.");
            int index = itemsSource.IndexOf(value);
            bool result = index >= 0;
            if (result) RemoveAt(index);
            return result;
        }

        public override void Update()
        {
            foreach (Item item in virtualItems.Values)
            {
                item.Update();
            }
            base.Update();
        }

        #endregion

        #region IItemHost Members

        public void SetItemHeight(int index, int height)
        {
            // not supported for ListViewGroup..
        }

        public void OnTapItem(Item item)
        {
            ListView.OnTapItem(item);
        }

        /// <summary>
        /// Brings an item into view.
        /// </summary>
        /// <param name="itemIndex">Index of the item  to bring into view.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        /// <exception cref="ArgumentOutOfRangeException">if itemIndex is outside bounds.</exception>
        public virtual void BringItemIntoView(int itemIndex, bool animated)
        {
            Rectangle rect = GetItemRect(itemIndex);
            Rectangle headerRect = GetHeaderRect();
            rect.Offset(0, headerRect.Height);
            Host.BringItemRectIntoView(this.ItemIndex, rect, animated);
        }

        /// <summary>
        /// Brings the specified rectangle within an item into view.
        /// </summary>
        /// <param name="itemIndex">Index of the item  to bring into view.</param>
        /// <param name="rect">The rectangle of the client bounds in the item to bring into view.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        /// <exception cref="ArgumentOutOfRangeException">if itemIndex is outside bounds.</exception>
        public virtual void BringItemRectIntoView(int itemIndex, Rectangle rect, bool animated)
        {
            rect.Offset(X, Y);
            Host.BringItemRectIntoView(itemIndex, rect, animated);
        }

        /// <summary>
        /// Determines whether an item is selected by the host.
        /// </summary>
        /// <param name="item">item to determine state.</param>
        /// <returns>True, if the item is selected, otherwise false.</returns>
        public virtual bool IsItemSelected(Item item)
        {
            return ListView.IsItemSelected(item);
        }

        #endregion

        #region IItemHost Members

        bool IItemHost.IsDynamicItemHeightSupported
        {
            get { return false; }
        }

        #endregion
    }
}
